#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <event2/util.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/bufferevent_struct.h>

#include "log.h"
#include "wss.h"
#include "frame.h"

#define LISTN_PORT  20000

static void readcb (struct bufferevent *bev, void *ctx);
static void eventcb (struct bufferevent *bev, short events, void *ctx);
static void accept_cb(struct evconnlistener *listener, evutil_socket_t fd,
                        struct sockaddr *addr, int slen, void *arg);

int main (int argc, char **argv)
{
    struct sockaddr_in          *sin;
    struct event_base           *ebase;
    struct evconnlistener       *listener;
    struct sockaddr_storage      listen_addr;
    int                          socklen;

    if( log_open("console", LOG_LEVEL_TRACE, 1024, LOG_LOCK_DISABLE) < 0 )
        return 1;

    log_info("websockets web server listen on port [%d]\n", LISTN_PORT);
    log_info("GuoWenxue <guowenxue@gmail.com>\n\n");

    if( !(ebase=event_base_new() ))
    {
        log_fatal("event_base_new() failure\n");
        return 2;
    }

    sin = (struct sockaddr_in*)&listen_addr;
    sin->sin_port = htons(LISTN_PORT);
    sin->sin_addr.s_addr = htonl(INADDR_ANY);
    sin->sin_family = AF_INET;
    socklen = sizeof(struct sockaddr_in);


    /* Allocate a new evconnlistener object to listen for incoming TCP connections on a given address
     * @arg1: The event base to associate the listener with.
     * @arg2: A callback to be invoked when a new connection arrives.If it's NULL,
     *        the listener will be treated as disabled until the callback is set.
     * @arg3: A user-supplied pointer to give to the callback.
     * @arg4: Any number of LEV_OPT_* flags
     * @arg5: backlog Passed to the listen() call to determine the length of the
     *        acceptable connection backlog.  Set to -1 for a reasonable default.
     * @arg6: The address to listen for connections on.
     * @arg7: The length of the address.
     */
    listener = evconnlistener_new_bind(ebase, accept_cb, ebase,
            LEV_OPT_CLOSE_ON_FREE|LEV_OPT_CLOSE_ON_EXEC|LEV_OPT_REUSEABLE,
            -1, (struct sockaddr*)&listen_addr, socklen);
    if ( !listener || evconnlistener_get_fd(listener)<0)
    {
        log_fatal("evconnlistener_new_bind failure.\n");
        event_base_free(ebase);
        return 3;
    }

    event_base_dispatch(ebase);

    evconnlistener_free(listener);
    event_base_free(ebase);

    return 0;
}

/**************************************************************************************
 *  Description: The callback function that we invoke when a listener has a new connection.
 *   Input Args: @listener: The evconnlistener fd: Accept new client fd
 *               @addr: The source address of the connection socklen: The length of addr
 *               @arg: the pointer passed to evconnlistener_new()
 *  Output Args: NULL
 * Return Value: NONE
 *************************************************************************************/
static void accept_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int slen, void *arg)
{
    struct event_base               *ebase = arg;
    struct bufferevent              *bev_accpt;
    struct sockaddr_in              *sock = (struct sockaddr_in *)addr;
    wss_session_t                   *session;

    if( !(session = malloc(sizeof(*session))) )
    {
        log_error("malloc for session failure:%s\n", strerror(errno));
        close(fd);
        return ;
    }

    memset(session, 0, sizeof(*session));
    snprintf(session->client, sizeof(session->client), "[%d->%s:%d]", fd, inet_ntoa(sock->sin_addr), ntohs(sock->sin_port));
    log_info("accpet new socket client %s\n", session->client);

    bev_accpt = bufferevent_socket_new(ebase, fd, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS);
    if( !bev_accpt )
    {
        log_error("create bufferevent for client for %s failed\n", session->client);
        return;
    }

    session->bev = bev_accpt;
    log_trace("create bufferevent for client for %s okay\n", session->client);

    /* bufferevent_setcb:  Changes the callbacks for a bufferevent.
     * @arg1: the bufferevent object for which to change callbacks
     * @arg2: readcb callback to invoke when there is data to be read
     * @arg3: writecb callback to invoke when the file descriptor is ready for writing
     * @arg4: eventcb callback to invoke when there is an event on the file descriptor
     * @arg5: cbarg an argument that will be supplied to each of above callbacks
     */
    bufferevent_setcb(bev_accpt, readcb, NULL, eventcb, session);

    /* bufferevent_enable: Enable a bufferevent.
     * @arg1: bufev the bufferevent to be enabled
     * @arg2: event any combination of EV_READ | EV_WRITE.
     */
    bufferevent_enable(bev_accpt, EV_READ|EV_WRITE);

    return;
}

/**************************************************************************************
 *  Description: read callback is triggered when new data arrives in the input
 *               buffer and the amount of readable data exceed the low watermark
 *               which is 0 by default.
 *   Input Args: @bev: the bufferevent that triggered the callback
 *               @the user-specified context for this bufferevent
 *  Output Args: NONE
 * Return Value: NONE
 *************************************************************************************/
static void readcb (struct bufferevent *bev, void *ctx)
{
    wss_session_t              *session = bev->cbarg;

    if( !session->handshaked )
    {
        do_wss_handshake(session);
        return ;
    }

    do_parser_frames(session);

    return ;
}

static void eventcb (struct bufferevent *bev, short events, void *ctx)
{
    wss_session_t              *session = bev->cbarg;

    if( events&(BEV_EVENT_EOF|BEV_EVENT_ERROR) )
    {
        if( session )
            log_warn("remote client %s closed\n", session->client);

        bufferevent_free(bev);
    }

    return ;
}
